import org.junit.jupiter.api.Test;

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;

/**
 * ZonedDateTime类是具有偏移量和时区两个概念的日期时间，由dateTime、offset和zone三个变量组成。其中dateTime是LocalDateTime类的对象，表示日期时间，
 * offset是ZoneOffset类的对象，用于表示dateTime对象相对于UTC的偏移量，zone属于ZoneId类，可以是ZoneRegion类的对象（表示时区），也可以是ZoneOffset，此时没有时区的概念。
 * ”+08:00“表示比UTC时间快8小时，”-08:00“表示比UTC时间慢8小时。
 *
 * ZonedDateTime类在功能上跟OffsetDateTime类差不多，但ZonedDateTime类在时区转换上使用起来跟方便。
 * 例如OffsetDateTime类可以使用withOffsetSameInstant(ZoneOffset offset)方法将北京时间切换成格林威治时间，
 * 但前提是你得需要知道北京时间跟格林威治时间的时差。再比如北京时间切换成洛杉矶时间，在夏令时，北京时间比洛杉矶时间快15小时，
 * 在冬令时，北京时间比洛杉矶时间快16小时，那么使用OffsetDateTime在北京时间与洛杉矶时间相互切换时会更麻烦。
 * 而ZonedDateTime类引入了时区的概念后，我们不再需要关心时区之间时差的问题，也无需关心不同令时不同时差的问题，我们只需关系从哪个时区切换到哪个时区。
 */
public class ZonedDateTimeDemo {

}

class ZonedDateTimeDemo01 {

    /**
     * from(TemporalAccessor temporal)
     * 作用：从其他具有偏移量或时区概念的时间对象获取 zonedDateTime 对象。
     */
    @Test
    public void test01(){
        OffsetDateTime offsetDateTime01 = OffsetDateTime.parse("1970-01-01T08:00:00+08:00"); // UTC时间 1970-01-01T00:00:00
        ZonedDateTime zonedDateTime01 = ZonedDateTime.from(offsetDateTime01);
        System.out.println(zonedDateTime01); // 输出 1970-01-01T08:00+08:00
    }

    /**
     * now()
     * 作用：从操作系统获取 zonedDateTime 对象。
     *
     * now(ZoneId zone)
     * 作用：获取指定时区此刻的日期时间。
     *
     * now(Clock clock)
     * 作用：从 clock 对象获取 zonedDateTime 对象。clock 对象有时区的概念，
     *      获取的是 clock 对象指定时区的日期时间。
     */
    @Test
    public void test02(){
        ZoneId zoneId01 = ZoneId.of("America/Los_Angeles");
        ZonedDateTime zonedDateTime01 = ZonedDateTime.now();
        System.out.println(zonedDateTime01); // 输出 2023-05-02T22:39:07.435788600+08:00[Asia/Shanghai]

        ZonedDateTime zonedDateTime02 = ZonedDateTime.now(zoneId01);
        System.out.println(zonedDateTime02); // 输出 2023-05-02T07:39:07.435788600-07:00[America/Los_Angeles]

        Instant instant01 = Instant.parse("2023-04-01T10:00:00Z");
        Clock fixedClock01 = Clock.fixed(instant01, zoneId01);
        ZonedDateTime zonedDateTime03 = ZonedDateTime.now(fixedClock01);
        System.out.println(zonedDateTime03); // 输出 2023-04-01T03:00-07:00[America/Los_Angeles]
    }

    /**
     * of(int year, int month, int dayOfMonth, int hour, int minute, int second, int nanoOfSecond, ZoneId zone)
     * of(LocalDate date, LocalTime time, ZoneId zone)
     * of(LocalDateTime localDateTime, ZoneId zone)
     * 作用：给定日期时间和地区/偏移量数据，创建 zonedDateTime 对象。
     *      dateTime和zone只是组合关系。
     */
    @Test
    public void test03(){
        LocalDateTime localDateTime01 = LocalDateTime.parse("2023-01-01T00:00:00");
        ZoneId zoneRegion01 = ZoneId.of("Asia/Shanghai");
        ZonedDateTime zonedDateTime01 = ZonedDateTime.of(localDateTime01, zoneRegion01);
        System.out.println(zonedDateTime01); // 输出 2023-01-01T00:00+08:00[Asia/Shanghai]
    }

    /**
     * ofInstant(Instant instant, ZoneId zone)
     * 作用：instant 是 UTC 时间轴上的一个瞬时值，被当成 UTC 时间来看待。
     *      该方法会将 instant 时间转换成对应时区的时间。
     *
     * ofInstant(LocalDateTime localDateTime, ZoneOffset offset, ZoneId zone)
     * 作用：如果 zone 和 offset 匹配，那么 localDateTime、offset 和 zone 只是组合关系。
     *      如果不匹配，那么 localDateTime 会根据 offset 偏移量转成 UTC 时间，然后再转成对应的时区的时间。
     */
    @Test
    public void test04(){
        LocalDateTime localDateTime01 = LocalDateTime.parse("2023-01-01T00:00:00");
        Instant instant01 = Instant.ofEpochSecond(0, 0);
        ZoneId zoneRegion01 = ZoneId.of("Asia/Shanghai");
        ZoneOffset zoneOffset01 = ZoneOffset.of("+08:00");
        ZoneOffset zoneOffset02 = ZoneOffset.of("+10:00");

        System.out.println(instant01); // 输出 1970-01-01T00:00:00Z
        ZonedDateTime zonedDateTime01 = ZonedDateTime.ofInstant(instant01, zoneRegion01);
        ZonedDateTime zonedDateTime02 = ZonedDateTime.ofInstant(instant01, zoneOffset02);
        System.out.println(zonedDateTime01); // 输出 1970-01-01T08:00+08:00[Asia/Shanghai]
        System.out.println(zonedDateTime02); // 输出 1970-01-01T10:00+10:00

        ZonedDateTime zonedDateTime03 = ZonedDateTime.ofInstant(localDateTime01, zoneOffset01, zoneRegion01);
        ZonedDateTime zonedDateTime04 = ZonedDateTime.ofInstant(localDateTime01, zoneOffset02, zoneRegion01);
        System.out.println(zonedDateTime03); // 输出 2023-01-01T00:00+08:00[Asia/Shanghai]
        System.out.println(zonedDateTime04); // 输出 2022-12-31T22:00+08:00[Asia/Shanghai]
    }

    /**
     * ofLocal(LocalDateTime localDateTime, ZoneId zone, ZoneOffset preferredOffset)
     * 作用：作用跟 of(LocalDateTime localDateTime, ZoneId zone) 类似，dateTime 和 zone 只是组合关系。
     *      不同的是某些时区在特殊时间段跟 UTC 会有多个的时差。preferredOffset 用来指定使用哪个时差，
     *      如果为 null 默认选择第一个时差。例如美国洛杉矶时间分夏令时和冬令时，夏令时比 UTC 慢 7 小时，
     *      冬令时比 UTC 慢 8 小时，在夏令时转冬令时的特殊时间段跟 UTC 有两个时差，分别是 -07:00 和 -08:00 。
     *      preferredOffset 对象用来确定使用哪个时差。
     *
     * ofStrict(LocalDateTime localDateTime, ZoneOffset offset, ZoneId zone)
     * 作用：作用跟 ofInstant(LocalDateTime localDateTime, ZoneOffset offset, ZoneId zone)方法类似，
     *      不同的是 ofStrict() 方法会严格校验偏移量和区域的组合，如果不匹配会报错，不像 ofInstant() 方法会另外处理。
     */
    @Test
    public void test05(){
        LocalDateTime localDateTime01 = LocalDateTime.parse("2023-01-01T00:00:00");
        LocalDateTime localDateTime02 = LocalDateTime.parse("2023-11-05T01:00:00");
        ZoneId zoneRegion01 = ZoneId.of("Asia/Shanghai");
        ZoneId zoneRegion02 = ZoneId.of("America/Los_Angeles");
        ZoneOffset zoneOffset01 = ZoneOffset.of("+08:00");
        ZoneOffset zoneOffset02 = ZoneOffset.of("+10:00");
        ZoneOffset zoneOffset03 = ZoneOffset.of("-08:00");
        ZoneOffset zoneOffset04 = ZoneOffset.of("-07:00");

        ZonedDateTime zonedDateTime01 = ZonedDateTime.ofLocal(localDateTime02, zoneRegion02, zoneOffset03);
        ZonedDateTime zonedDateTime02 = ZonedDateTime.ofLocal(localDateTime02, zoneRegion02, zoneOffset04);
        System.out.println(zonedDateTime01); // 输出 2023-11-05T01:00-08:00[America/Los_Angeles]
        System.out.println(zonedDateTime02); // 输出 2023-11-05T01:00-07:00[America/Los_Angeles]

        ZonedDateTime zonedDateTime03 = ZonedDateTime.ofStrict(localDateTime01, zoneOffset01, zoneRegion01);
        ZonedDateTime zonedDateTime04 = ZonedDateTime.ofStrict(localDateTime01, zoneOffset02, zoneRegion01);
        System.out.println(zonedDateTime03); // 输出 2023-01-01T00:00+08:00[Asia/Shanghai]
        System.out.println(zonedDateTime04); // 报错 java.time.DateTimeException: ZoneOffset '+10:00' is not valid for LocalDateTime '2023-01-01T00:00' in zone 'Asia/Shanghai'
    }



    /**
     * parse(CharSequence text)
     * parse(CharSequence text, DateTimeFormatter formatter)
     * 作用：将字符串解析成 zonedDateTime 对象，默认格式 yyyy-MM-ddTHH:mm:ssxxx'['VV']'，
     *      其中 xxx 表示偏移量，[VV] 表示时区，formatter 用来指定字符串格式。
     */
    @Test
    public void test06(){
        ZonedDateTime zonedDateTime01 = ZonedDateTime.parse("2023-01-01T12:00:00+08:00[Asia/Shanghai]");
        System.out.println(zonedDateTime01); // 输出 2023-01-01T12:00+08:00[Asia/Shanghai]

        DateTimeFormatter df01 = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm:ssxxx '['VV']'");
        ZonedDateTime zonedDateTime02 = ZonedDateTime.parse("20230101 12:00:00+08:00 [Asia/Shanghai]", df01);
        System.out.println(zonedDateTime02); // 输出 2023-01-01T12:00+08:00[Asia/Shanghai]
    }
}

class ZonedDateTimeDemo03 {

    /**
     * format(DateTimeFormatter formatter)
     * 作用：将 zonedDateTime 对象按指定格式输出。
     */
    @Test
    public void test01(){
        DateTimeFormatter df01 = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:ss:dd 偏移量：xxx 时区：'['VV']'");
        ZonedDateTime dateTime01 = ZonedDateTime.parse("2023-01-01T12:00:00+08:00[Asia/Shanghai]");
        System.out.println(dateTime01); // 输出 2023-01-01T12:00+08:00[Asia/Shanghai]
        System.out.println(dateTime01.format(df01)); // 输出 2023/01/01 12:00:01 偏移量：+08:00 时区：[Asia/Shanghai]（不知道为什么结果多了一秒）
    }

    /**
     * getZone()
     * 作用：获取时区对象。
     */
    @Test
    public void test02() {
        ZonedDateTime dateTime01 = ZonedDateTime.parse("1970-01-01T08:00:00+08:00[Asia/Shanghai]");
        ZoneId zoneId = dateTime01.getZone();
        System.out.println(zoneId); // 输出 Asia/Shanghai
    }

    /**
     * toLocalDate()
     * toLocalDateTime()
     * toLocalTime()
     * toOffsetDateTime()
     * 作用：见名知意，通过 zonedDateTime 对象获取其他时间类对象。
     */
    @Test
    public void test03(){
        ZonedDateTime dateTime01 = ZonedDateTime.parse("1970-01-01T08:00:00+08:00[Asia/Shanghai]");
        System.out.println(dateTime01.toLocalDate()); // 输出 1970-01-01
        System.out.println(dateTime01.toLocalTime()); // 输出 08:00
        System.out.println(dateTime01.toOffsetDateTime()); // 输出 1970-01-01T08:00+08:00
    }

    /**
     * truncatedTo(TemporalUnit unit)
     * 作用：根据时间单位截取 zonedDateTime 对象。
     */
    @Test
    public void test04(){
        ZonedDateTime dateTime01 = ZonedDateTime.parse("1970-01-01T12:30:45+08:00[Asia/Shanghai]");
        System.out.println(dateTime01); // 输出 1970-01-01T12:30:45+08:00[Asia/Shanghai]
        System.out.println(dateTime01.truncatedTo(ChronoUnit.MINUTES)); // 输出 1970-01-01T12:30+08:00[Asia/Shanghai]
        System.out.println(dateTime01.truncatedTo(ChronoUnit.HOURS)); // 输出 1970-01-01T12:00+08:00[Asia/Shanghai]
    }

    /**
     * until(Temporal endExclusive, TemporalUnit unit)
     * 作用：根据指定的时间单位计算两个 zonedDateTime 对象的差值。
     */
    @Test
    public void test05(){
        ZonedDateTime dateTime01 = ZonedDateTime.parse("1970-01-01T00:00:00+08:00[Asia/Shanghai]");
        ZonedDateTime dateTime02 = ZonedDateTime.parse("1970-01-01T01:00:00+08:00[Asia/Shanghai]");
        System.out.println(dateTime01.until(dateTime02, ChronoUnit.HOURS)); // 输出 1
        System.out.println(dateTime01.until(dateTime02, ChronoUnit.MINUTES)); // 输出 60
    }

    /**
     * withEarlierOffsetAtOverlap()
     * withLaterOffsetAtOverlap()
     * 作用：由于夏令时等因素，同一时区在某段时间内会出现两个不同的偏移量。
     *      以上方法用来解决二义性的问题，调用以上方法用来确认使用哪个偏移量。
     *
     * withFixedOffsetZone()
     * 作用：调整为只带偏移量，不带时区概念的 zonedDateTime 对象，
     *      可以忽略时区带来夏令时等因素的影响。
     */
    @Test
    public void test06(){
        LocalDateTime localDateTime01 = LocalDateTime.parse("2023-11-05T01:00:00");
        ZoneId zoneId = ZoneId.of("America/Los_Angeles");
        ZonedDateTime zonedDateTime01 = ZonedDateTime.of(localDateTime01, zoneId);
        ZonedDateTime zonedDateTime02 = zonedDateTime01.withLaterOffsetAtOverlap();
        System.out.println(zonedDateTime01); // 输出 2023-11-05T01:00-07:00[America/Los_Angeles]
        System.out.println(zonedDateTime02); // 输出 2023-11-05T01:00-08:00[America/Los_Angeles]

        ZonedDateTime zoneddateTime03 = zonedDateTime01.withFixedOffsetZone();
        System.out.println(zoneddateTime03); // 输出 2023-11-05T01:00-07:00
    }

    /**
     * withZoneSameInstant(ZoneId zone)
     * 作用：通过调整时区或偏移量来调整 zonedDateTime 对象，
     *      但不管如何调整，转换成的 UTC 时间不变。
     *
     * withZoneSameLocal(ZoneId zone)
     * 作用：时间不变，仅改变时区或偏移量。
     */
    @Test
    public void test07(){
        ZonedDateTime dateTime01 = ZonedDateTime.parse("2023-01-01T16:00:00+08:00[Asia/Shanghai]");
        ZoneId zoneId01 = ZoneId.of("America/Los_Angeles");
        System.out.println(dateTime01.withZoneSameInstant(zoneId01)); // 输出 2023-01-01T00:00-08:00[America/Los_Angeles]
        System.out.println(dateTime01.withZoneSameLocal(zoneId01)); // 输出 2023-01-01T16:00-08:00[America/Los_Angeles]
    }
}
